#include <stdio.h>
#include <stdlib.h>

#include "calc.h"

static Tree make_number_node(const char s[]);
static Tree make_op_node(const char s, Tree l, Tree r);
Tree parse_factor()
{
    Tree t = NULL;
    int sign = 1;
    if (get_token(&gtoken) != T_EOF) {
        if (gtoken.type == T_SUB) {
            sign = -1;
            get_token(&gtoken);
        }
        switch (gtoken.type) {
            case T_NUM:
                t = make_number_node(gtoken.tkbuf);
                get_token(&gtoken);
                break;
                // return t;
            case T_LP:
                t = parse_expr();
                if (gtoken.type != T_RP) {
                    fprintf(stderr, "closing parenthese expected.\n");
                    exit(1);
                } else
                    get_token(&gtoken);
                break;
                // return t;
            default:
                fprintf(stderr, "unexpected token: %s\n", gtoken.tkbuf);
                exit(1);
        }
        if (sign == 1)
            return t;
        else if (sign == -1)
            return make_op_node('-', t, NULL);
    }
    return t;
}

static Tree make_number_node(const char s[])
{
    Tree t = (Tree) calloc(sizeof(Node), 1);
    double val;
    char *p;

    if (!t) {
        fprintf(stderr, "calloc failed.\n");
        exit(1);
    }
    val = strtod(s, &p);
    if (*p != '\0') {
        fprintf(stderr, "inproper number.\n");
        exit(1);
    }
    t->value = val;
    t->left = t->right = NULL;
    t->type = '0';

    return t;
}

static Tree make_op_node(const char s, Tree l, Tree r)
{
    Tree t = (Tree) calloc(sizeof(Node), 1);

    if (!t) {
        fprintf(stderr, "calloc failed.\n");
        exit(1);
    }

    t->left = l;
    t->right = r;
    t->type = s;

    return t;
}

Tree parse_term()
{
    Tree fact = NULL;
    Tree term = NULL;
    char p;
    fact = parse_factor();
    while (1) {
        switch (gtoken.type) {
        case T_MULTI:
        case T_DIV:
            p = gtoken.tkbuf[0];
            term = parse_factor();
            fact = make_op_node(p, fact, term);
            break;
        default:
            return fact;
        }
    }
}

Tree parse_expr()
{
    Tree exp = NULL;
    Tree term = NULL;
    char p;
    term = parse_term();
    while (1) {
        switch (gtoken.type) {
        case T_ADD:
        case T_SUB:
            p = gtoken.tkbuf[0];
            exp = parse_term();
            term = make_op_node(p, term, exp);
            break;
        default:
            return term;
        }
    }
}

calc_t eval_ast(Tree ast)
{
    switch (ast->type) {
    case '0':
        return ast->value;
    case '+':
        return eval_ast(ast->left) + eval_ast(ast->right);
    case '-':
        if (ast->right != NULL)
            return eval_ast(ast->left) - eval_ast(ast->right);
        else
            return -1 * eval_ast(ast->left);
    case '*':
        return eval_ast(ast->left) * eval_ast(ast->right);
    case '/':
        return eval_ast(ast->left) / eval_ast(ast->right);
    default:
        fprintf(stderr, "unkown node type.\n");
        exit(1);
    }
}

void free_ast(Tree ast)
{
    if (!ast)
        return;
    free_ast(ast->left);
    free_ast(ast->right);
    free(ast);
}
